#include "GlDxSprite.h"

#include "GlMatrix4x4.h"
#include "GlVector4.h"
#include "GlPosition.h"
#include "GlSpriteDescription.h"
#include "GlSpriteDescriptionManager.h"
#include "GlSpriteRenderer.h"
#include "GlTextureManager.h"

namespace GraphicLibrary
{
	//---------------------------------------------------
	DxSprite::DxFactory::~DxFactory()
	{
	}

	//---------------------------------------------------
	Sprite* DxSprite::DxFactory::create(const char *spriteName)
	{
		return new DxSprite(mDevice, spriteName);
	}
}

namespace GraphicLibrary
{
	//---------------------------------------------------
	DxSprite::DxSprite(ID3D11Device *device, const char *spriteName)
		: mDesc(0)
		, mVertexBuffer(0)
		, mIndexBuffer(0)
		, mTexture(0)
		, mFrameIndex(0)
		, mTimeSinceFrameStarted(0.0f)
		, mDoAnimation(true)
		, mSizeMul(1.0f, 1.0f)
		, mScale(1.0f, 1.0f)
		, mTransparency(0.0f)
	{
		setSelf(this);
		mDesc = SpriteDescriptionManager::GetSingleton()->getDesc(spriteName);
		mDoAnimation = mDesc->getFramePositions().size() > 1 && mDesc->getPlayTimeInSeconds() > 0.0f;
		if(!mDesc)
			throw ElSpriteNameNotFound;
		initTexture(mDesc->getTexFileName());
		mVertexBuffer = createVertexBuffer(device);
		mIndexBuffer = createIndexBuffer(device);
	}

	//---------------------------------------------------
	DxSprite::~DxSprite()
	{
		if(mVertexBuffer)
			mVertexBuffer->Release();
		if(mIndexBuffer)
			mIndexBuffer->Release();
	}

	//---------------------------------------------------
	void DxSprite::setTransparency(const float value)
	{
		mTransparency = value;
	}

	//---------------------------------------------------
	float DxSprite::getTransparency() const
	{
		return mTransparency;
	}

	//---------------------------------------------------
	Size DxSprite::getSize() const
	{
		return Size(mDesc->getFrameSize().mWidth * mSizeMul.mWidth, mDesc->getFrameSize().mHeight * mSizeMul.mHeight);
	}

	//---------------------------------------------------
	void DxSprite::setSize(const Size &newSize)
	{
		mSizeMul.mHeight = newSize.mHeight / mDesc->getFrameSize().mHeight;
		mSizeMul.mWidth = newSize.mWidth / mDesc->getFrameSize().mWidth;
	}

	//---------------------------------------------------
	void DxSprite::setScale(const Size &newScale)
	{
		mScale = newScale;
	}

	//---------------------------------------------------
	Size DxSprite::getScale() const
	{
		return mScale;
	}

	//---------------------------------------------------
	bool DxSprite::contains(const Position &pos) const
	{
		Matrix4x4 invTrans = getInvTransformationMatrix();
		Vector4 vec;
		vec(0) = pos.mX;
		vec(1) = pos.mY;
		vec(3) = 0.0f;
		vec(3) = 1.0f;
		vec = vec * invTrans;

		return Utility::isInBound(mDesc->getFrameSize().mWidth * -0.5f, mDesc->getFrameSize().mWidth * 0.5f, vec(0)) && Utility::isInBound(mDesc->getFrameSize().mHeight * -0.5f, mDesc->getFrameSize().mHeight * 0.5f, vec(1));
	}

	//---------------------------------------------------
	void DxSprite::tick(const float deltaTime)
	{
		if(!mDoAnimation)
			return;

		mTimeSinceFrameStarted += deltaTime;
		float frameDuration = mDesc->getPlayTimeInSeconds() / mDesc->getFramePositions().size();
		while(mTimeSinceFrameStarted >= frameDuration)
		{
			mTimeSinceFrameStarted -= frameDuration;
			mFrameIndex = (mFrameIndex + 1) % mDesc->getFramePositions().size();
		}
	}

	//---------------------------------------------------
	Matrix4x4 DxSprite::getTransformationMatrix() const
	{
		Matrix4x4 mat;
		mat.setScale(getSizeMul().mWidth * getScale().mWidth, getSizeMul().mHeight * getScale().mHeight, 1.0f);
		Matrix4x4 tmp;
		tmp.setRotationZ(getRotateAngle());
		mat *= tmp;
		tmp.setTranslocation(getX(), getY(), 0.0f);
		mat *= tmp;
		return mat;
	}

	//---------------------------------------------------
	void DxSprite::apply(ID3D11DeviceContext *ic) const
	{
		ic->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
		UINT strides[] = {
			sizeof(VertexData)
		};
		UINT offsets[] = {
			0
		};
		ic->IASetVertexBuffers(0, 1, &mVertexBuffer, strides, offsets);
		ic->PSSetShaderResources(0, 1, &mTexture);
	}

	//---------------------------------------------------
	Position DxSprite::getUvShift() const
	{
		Position p = mDesc->getFramePositions()[mFrameIndex];
		p.mX /= mTextureSize.mWidth;
		p.mY /= mTextureSize.mHeight;
		return p;
	}

	//---------------------------------------------------
	Size DxSprite::getTextureRelativeFrameSize() const
	{
		Size size;
		size.mWidth = mDesc->getFrameSize().mWidth / mTextureSize.mWidth;
		size.mHeight = mDesc->getFrameSize().mHeight / mTextureSize.mHeight;
		return size;
	}

	//---------------------------------------------------
	float DxSprite::getBoundingCircleRadius() const
	{
		Size size = mDesc->getFrameSize();
		size.mWidth *= mSizeMul.mWidth * mScale.mWidth;
		size.mHeight *= mSizeMul.mHeight * mScale.mHeight;
		Position corner = Position(size.mWidth * 0.5f, size.mHeight * 0.5f);
		return corner.length();
	}

	//---------------------------------------------------
	void DxSprite::fillVertexes(Position *vertexes) const
	{
		
		vertexes[0].mX = mDesc->getFrameSize().mWidth * -0.5f;
		vertexes[0].mY = mDesc->getFrameSize().mHeight * 0.5f;

		vertexes[1].mX = mDesc->getFrameSize().mWidth * 0.5f;
		vertexes[1].mY = mDesc->getFrameSize().mHeight * 0.5f;

		vertexes[2].mX = mDesc->getFrameSize().mWidth * 0.5f;
		vertexes[2].mY = mDesc->getFrameSize().mHeight * -0.5f;

		vertexes[3].mX = mDesc->getFrameSize().mWidth * -0.5f;
		vertexes[3].mY = mDesc->getFrameSize().mHeight * -0.5f;
	}

	//---------------------------------------------------
	ID3D11Buffer* DxSprite::createVertexBuffer(ID3D11Device *device) const
	{
		VertexData data[4];

		data[0].mPos[0] = mDesc->getFrameSize().mWidth * -0.5f;
		data[0].mPos[1] = mDesc->getFrameSize().mHeight * 0.5f;
		data[0].mUV[0] = 0.0f;
		data[0].mUV[1] = 0.0f;

		data[1].mPos[0] = mDesc->getFrameSize().mWidth * 0.5f;
		data[1].mPos[1] = mDesc->getFrameSize().mHeight * 0.5f;
		data[1].mUV[0] = 1.0f;
		data[1].mUV[1] = 0.0f;

		data[2].mPos[0] = mDesc->getFrameSize().mWidth * 0.5f;
		data[2].mPos[1] = mDesc->getFrameSize().mHeight * -0.5f;
		data[2].mUV[0] = 1.0f;
		data[2].mUV[1] = 1.0f;

		data[3].mPos[0] = mDesc->getFrameSize().mWidth * -0.5f;
		data[3].mPos[1] = mDesc->getFrameSize().mHeight * -0.5f;
		data[3].mUV[0] = 0.0f;
		data[3].mUV[1] = 1.0f;

		UINT size = sizeof(VertexData) * 4;
		return DxGraphicComponent::createVertexBuffer(device, size, data);
	}

	//---------------------------------------------------
	ID3D11Buffer* DxSprite::createIndexBuffer(ID3D11Device *device) const
	{
		IndexData data[6];
		data[0] = 0;
		data[1] = 1;
		data[2] = 2;
		data[3] = 2;
		data[4] = 3;
		data[5] = 0;

		UINT size = sizeof(IndexData) * 6;
		return DxGraphicComponent::createIndexBuffer(device, size, data);
	}

	//---------------------------------------------------
	void DxSprite::initTexture(const char *fileName)
	{
		mTexture = TextureManager::GetSingleton()->getTexture(fileName);

		ID3D11Texture2D *tex = 0;
		mTexture->GetResource(reinterpret_cast<ID3D11Resource**>(&tex));
		D3D11_TEXTURE2D_DESC desc;
		tex->GetDesc(&desc);
		mTextureSize.mWidth = static_cast<float>(desc.Width);
		mTextureSize.mHeight = static_cast<float>(desc.Height);
	}

	//---------------------------------------------------
	Matrix4x4 DxSprite::getInvTransformationMatrix() const
	{
		Matrix4x4 mat;
		mat.setTranslocation(-getX(), -getY(), 0.0f);
		Matrix4x4 tmp;
		tmp.setRotationZ(-getRotateAngle());
		mat *= tmp;
		tmp.setScale(1.0f / (getSizeMul().mWidth * getScale().mWidth), 1.0f / (getSizeMul().mHeight * getScale().mHeight), 1.0f);
		mat *= tmp;
		return mat;
	}
}
